/*
 * Copyright (C) 2009 Dimitrios C. Michalakos
 *
 * This program is free software: you can redistribute it and/or modify
 * it under the terms of the GNU General Public License as published by
 * the Free Software Foundation, either version 2 of the License, or
 * (at your option) any later version.
 *
 * This program is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License
 * along with this program.  If not, see <http://www.gnu.org/licenses/>.
 */
package com.restsql.db;

import java.sql.ResultSet;
import java.sql.ResultSetMetaData;
import java.sql.SQLException;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.Map;

/**
 * GenericColumnInfo can be extended by another class to get information about the types and properties of the columns in a table or a resultset.
 * @author dimitris@jmike.gr
 */
public class GenericColumnInfo implements ColumnInfo {

    protected Map<String, Integer> index = new HashMap<String, Integer>();//<column name, internal id>
    protected String[] column;
    protected String[] table;
    protected DatatypeEnum[] datatype;
    protected boolean[] signed;
    protected boolean[] nullable;
    protected boolean[] autoInc;
    protected boolean[] primaryKey;
    protected long[] size;
    protected int[] integerDigits;
    protected int[] fractionalDigits;
    protected String[] defaultValue;
    protected String[] possibleValues;

    protected ArrayList primaryKey_col_positions = new ArrayList();

    /**
     * Constructs a new GenericColumnInfo.
     */
    public GenericColumnInfo() {
    }

    /**
     * Parses the specified ResultSet and constructs a new GenericColumnInfo.
     * @param db the parent database.
     * @param rs the dataset.
     * @throws SQLException
     */
    public GenericColumnInfo(Database db, ResultSet rs) throws SQLException {
        ResultSetMetaData rsmd = rs.getMetaData();//get ResultSetMetaData from ResultSet
        /* count the columns in resultset */
        final int columnCount = rsmd.getColumnCount();
        /* set the types and properties */
        table = new String[columnCount];
        column = new String[columnCount];
        datatype = new DatatypeEnum[columnCount];
        signed = new boolean[columnCount];
        primaryKey = new boolean[columnCount];
        nullable = new boolean[columnCount];
        autoInc = new boolean[columnCount];
        size = new long[columnCount];
        integerDigits = new int[columnCount];
        fractionalDigits = new int[columnCount];
        defaultValue = new String[columnCount];
        possibleValues = new String[columnCount];
        
        for (int i = 1; i <= columnCount; i++) {
            final String tableName = rsmd.getTableName(i);//set table
            final String columnName = rsmd.getColumnName(i);//set column
            index.put(columnName, Integer.valueOf(i));//put (column name, internal id)
            ColumnInfo columnInfo = db.getColumnInfo(tableName);
            table[i - 1] = tableName;//set table
            column[i - 1] = columnName;//set column
            datatype[i - 1] = columnInfo.getDatatype(columnName);
            signed[i - 1] = columnInfo.isSigned(columnName);
            primaryKey[i - 1] = columnInfo.isPrimaryKey(columnName);

            if(columnInfo.isPrimaryKey(columnName)) {
                primaryKey_col_positions.add(i);
            }
            
            nullable[i - 1] = columnInfo.isNullable(columnName);
            autoInc[i - 1] = columnInfo.isAutoIncremented(columnName);
            size[i - 1] = columnInfo.getSize(columnName);
            integerDigits[i - 1] = columnInfo.getIntegerDigits(columnName);
            fractionalDigits[i - 1] = columnInfo.getFractionalDigits(columnName);
            defaultValue[i - 1] = columnInfo.getDefaultValue(columnName);
            possibleValues[i - 1] = columnInfo.getPossibleValues(columnName);
        }
      
    }


  
    /**
     * Indicates whether the designated column exists in the dataset.
     * @param column the name of the column.
     * @return true if column exists, false if not.
     */
    public boolean exists(String column) {
        return index.containsKey(column);
    }

    /**
     * Returns the number of columns in the dataset.
     * @return
     */
    public int getColumnCount() {
        return column.length;
    }

    /**
     * Returns the designated column's position in the dataset. The first column is 1, the second is 2, ...
     * @param column the name of the column.
     * If two or more columns in the dataset have the same name, then the program assumes that the first column is being requested.
     * @return
     * @throws SQLException
     */
    public int getPosition(String column) throws SQLException {
        if (index.containsKey(column)) {
            return index.get(column).intValue();
        } else {
            final SQLExceptionState state = SQLExceptionState.COLUMN_NOT_FOUND;
            throw new SQLException("Column " + column + " does not exist in dataset.", state.name(), state.code());
        }
    }

    /**
     * Returns an Array containing the name of the columns in the dataset.
     * @return A new String array.
     */
    public String[] getColumnArray() {
        return column;
    }

    /**
     * Returns the designated column's name.
     * @param position the first column is 1, the second is 2, ...
     * @return
     * @throws SQLException
     */
    public String getColumn(int position) throws SQLException {
        if (position <= column.length) {
            return column[position - 1];
        } else {
            final SQLExceptionState state = SQLExceptionState.COLUMN_NOT_FOUND;
            throw new SQLException("The dataset contains only " + String.valueOf(column.length) + " column(s).", state.name(), state.code());
        }
    }

    /**
     * Returns an Array containing the name of the tables in the dataset.
     * @return A new String array.
     */
    public String[] getTableArray() {
        return table;
    }

    /**
     * Returns the designated column's table name.
     * @param position the first column is 1, the second is 2, ...
     * @return
     * @throws SQLException
     */
    public String getTable(int position) throws SQLException {
        if (position <= column.length) {
            return table[position - 1];
        } else {
            final SQLExceptionState state = SQLExceptionState.COLUMN_NOT_FOUND;
            throw new SQLException("The dataset contains only " + String.valueOf(column.length) + " column(s).", state.name(), state.code());
        }
    }

    /**
     * Returns the designated column's table name.
     * @param column the name of the column.
     * @return
     * @throws SQLException
     */
    public String getTable(String column) throws SQLException {
        if (index.containsKey(column)) {
            return table[index.get(column).intValue()];
        } else {
            final SQLExceptionState state = SQLExceptionState.COLUMN_NOT_FOUND;
            throw new SQLException("Column " + column + " does not exist in dataset.", state.name(), state.code());
        }
    }

    /**
     * Returns the designated column's datatype.
     * @param column the name of the column.
     * If two or more columns in the dataset have the same name, then the program assumes that the first column is being requested.
     * @return
     * @throws SQLException
     */
    public DatatypeEnum getDatatype(String column) throws SQLException {
        if (index.containsKey(column)) {
            return datatype[index.get(column).intValue()];
        } else {
            final SQLExceptionState state = SQLExceptionState.COLUMN_NOT_FOUND;
            throw new SQLException("Column " + column + " does not exist in dataset.", state.name(), state.code());
        }
    }

    /**
     * Returns the designated column's datatype.
     * @param position the first column is 1, the second is 2, ...
     * @return
     * @throws SQLException
     */
    public DatatypeEnum getDatatype(int position) throws SQLException {
        if (position <= column.length) {
            return datatype[position - 1];
        } else {
            final SQLExceptionState state = SQLExceptionState.COLUMN_NOT_FOUND;
            throw new SQLException("The dataset contains only " + String.valueOf(column.length) + " column(s).", state.name(), state.code());
        }
    }

    /**
     * Indicates whether values in the designated column are signed numbers.
     * @param column the name of the column.
     * If two or more columns in the dataset have the same name, then the program assumes that the first column is being requested.
     * @return true if columns is signed, false if not
     * @throws SQLException
     */
    public boolean isSigned(String column) throws SQLException {
        if (index.containsKey(column)) {
            return signed[index.get(column).intValue()];
        } else {
            final SQLExceptionState state = SQLExceptionState.COLUMN_NOT_FOUND;
            throw new SQLException("Column " + column + " does not exist in dataset.", state.name(), state.code());
        }
    }

    /**
     * Indicates whether values in the designated column are signed numbers.
     * @param position the first column is 1, the second is 2, ...
     * @return
     * @throws SQLException
     */
    public boolean isSigned(int position) throws SQLException {
        if (position <= column.length) {
            return signed[position - 1];
        } else {
            final SQLExceptionState state = SQLExceptionState.COLUMN_NOT_FOUND;
            throw new SQLException("The dataset contains only " + String.valueOf(column.length) + " column(s).", state.name(), state.code());
        }
    }

    /**
     * Returns the designated column's total size in characters, bytes or digits.
     * @param column the name of the column.
     * If two or more columns in the dataset have the same name, then the program assumes that the first column is being requested.
     * @return
     * @throws SQLException
     */
    public long getSize(String column) throws SQLException {
        if (index.containsKey(column)) {
            return size[index.get(column).intValue()];
        } else {
            final SQLExceptionState state = SQLExceptionState.COLUMN_NOT_FOUND;
            throw new SQLException("Column " + column + " does not exist in dataset.", state.name(), state.code());
        }
    }

    /**
     * Returns the designated column's total size in characters, bytes or digits.
     * @param position the first column is 1, the second is 2, ...
     * @return
     * @throws SQLException
     */
    public long getSize(int position) throws SQLException {
        if (position <= column.length) {
            return size[position - 1];
        } else {
            final SQLExceptionState state = SQLExceptionState.COLUMN_NOT_FOUND;
            throw new SQLException("The dataset contains only " + String.valueOf(column.length) + " column(s).", state.name(), state.code());
        }
    }

    /**
     * Returns the designated column's integer digits.
     * @param column the name of the column.
     * If two or more columns in the dataset have the same name, then the program assumes that the first column is being requested.
     * @return
     * @throws SQLException
     */
    public int getIntegerDigits(String column) throws SQLException {
        if (index.containsKey(column)) {
            return integerDigits[index.get(column).intValue()];
        } else {
            final SQLExceptionState state = SQLExceptionState.COLUMN_NOT_FOUND;
            throw new SQLException("Column " + column + " does not exist in dataset.", state.name(), state.code());
        }
    }

    /**
     * Returns the designated column's integer digits.
     * @param position the first column is 1, the second is 2, ...
     * @return
     * @throws SQLException
     */
    public int getIntegerDigits(int position) throws SQLException {
        if (position <= column.length) {
            return integerDigits[position - 1];
        } else {
            final SQLExceptionState state = SQLExceptionState.COLUMN_NOT_FOUND;
            throw new SQLException("The dataset contains only " + String.valueOf(column.length) + " column(s).", state.name(), state.code());
        }
    }

    /**
     * Returns the designated column's fractional digits.
     * @param column the name of the column.
     * If two or more columns in the dataset have the same name, then the program assumes that the first column is being requested.
     * @return
     * @throws SQLException
     */
    public int getFractionalDigits(String column) throws SQLException {
        if (index.containsKey(column)) {
            return fractionalDigits[index.get(column).intValue()];
        } else {
            final SQLExceptionState state = SQLExceptionState.COLUMN_NOT_FOUND;
            throw new SQLException("Column " + column + " does not exist in dataset.", state.name(), state.code());
        }
    }

    /**
     * Returns the designated column's fractional digits.
     * @param position the first column is 1, the second is 2, ...
     * @return
     * @throws SQLException
     */
    public int getFractionalDigits(int position) throws SQLException {
        if (position <= column.length) {
            return fractionalDigits[position - 1];
        } else {
            final SQLExceptionState state = SQLExceptionState.COLUMN_NOT_FOUND;
            throw new SQLException("The dataset contains only " + String.valueOf(column.length) + " column(s).", state.name(), state.code());
        }
    }

    /**
     * Returns the designated column's default value as a String.
     * @param column the name of the column.
     * If two or more columns in the dataset have the same name, then the program assumes that the first column is being requested.
     * @return
     * @throws SQLException
     */
    public String getDefaultValue(String column) throws SQLException {
        if (index.containsKey(column)) {
            return defaultValue[index.get(column).intValue()];
        } else {
            final SQLExceptionState state = SQLExceptionState.COLUMN_NOT_FOUND;
            throw new SQLException("Column " + column + " does not exist in dataset.", state.name(), state.code());
        }
    }

    /**
     * Returns the designated column's default value as a String.
     * @param position the first column is 1, the second is 2, ...
     * @return
     * @throws SQLException
     */
    public String getDefaultValue(int position) throws SQLException {
        if (position <= column.length) {
            return defaultValue[position - 1];
        } else {
            final SQLExceptionState state = SQLExceptionState.COLUMN_NOT_FOUND;
            throw new SQLException("The dataset contains only " + String.valueOf(column.length) + " column(s).", state.name(), state.code());
        }
    }

    /**
     * Returns the designated column's possible values separated by comma.
     * @param column the name of the column.
     * If two or more columns in the dataset have the same name, then the program assumes that the first column is being requested.
     * @return
     * @throws SQLException
     */
    public String getPossibleValues(String column) throws SQLException {
        if (index.containsKey(column)) {
            return possibleValues[index.get(column).intValue()];
        } else {
            final SQLExceptionState state = SQLExceptionState.COLUMN_NOT_FOUND;
            throw new SQLException("Column " + column + " does not exist in dataset.", state.name(), state.code());
        }
    }

    /**
     * Returns the designated column's possible values separated by comma.
     * @param position the first column is 1, the second is 2, ...
     * @return
     * @throws SQLException
     */
    public String getPossibleValues(int position) throws SQLException {
        if (position <= column.length) {
            return possibleValues[position - 1];
        } else {
            final SQLExceptionState state = SQLExceptionState.COLUMN_NOT_FOUND;
            throw new SQLException("The dataset contains only " + String.valueOf(column.length) + " column(s).", state.name(), state.code());
        }
    }

    /**
     * Indicates whether the designated column is a part of the primary key.
     * @param column the name of the column.
     * If two or more columns in the dataset have the same name, then the program assumes that the first column is being requested.
     * @return true if column is part of a primary key, false if not.
     * @throws SQLException
     */
    public boolean isPrimaryKey(String column) throws SQLException {
        if (index.containsKey(column)) {
            return primaryKey[index.get(column).intValue()];
        } else {
            final SQLExceptionState state = SQLExceptionState.COLUMN_NOT_FOUND;
            throw new SQLException("Column " + column + " does not exist in dataset.", state.name(), state.code());
        }
    }

    /**
     * Indicates whether the designated column is a part of the primary key.
     * @param position the first column is 1, the second is 2, ...
     * @return
     * @throws SQLException
     */
    public boolean isPrimaryKey(int position) throws SQLException {
        if (position <= column.length) {
            return primaryKey[position - 1];
        } else {
            final SQLExceptionState state = SQLExceptionState.COLUMN_NOT_FOUND;
            throw new SQLException("The dataset contains only " + String.valueOf(column.length) + " column(s).", state.name(), state.code());
        }
    }

    /**
     * Indicates whether the designated column accepts NULL as a value.
     * @param column the name of the column.
     * If two or more columns in the dataset have the same name, then the program assumes that the first column is being requested.
     * @return true if column is part of a primary key, false if not.
     * @throws SQLException
     */
    public boolean isNullable(String column) throws SQLException {
        if (index.containsKey(column)) {
            return nullable[index.get(column).intValue()];
        } else {
            final SQLExceptionState state = SQLExceptionState.COLUMN_NOT_FOUND;
            throw new SQLException("Column " + column + " does not exist in dataset.", state.name(), state.code());
        }
    }

    /**
     * Indicates whether the designated column accepts NULL as a value.
     * @param position the first column is 1, the second is 2, ...
     * @return
     * @throws SQLException
     */
    public boolean isNullable(int position) throws SQLException {
        if (position <= column.length) {
            return nullable[position - 1];
        } else {
            final SQLExceptionState state = SQLExceptionState.COLUMN_NOT_FOUND;
            throw new SQLException("The dataset contains only " + String.valueOf(column.length) + " column(s).", state.name(), state.code());
        }
    }

    /**
     * Indicates whether the designated numeric column is automatically incremented.
     * @param column the name of the column.
     * If two or more columns in the dataset have the same name, then the program assumes that the first column is being requested.
     * @return true if column is part of a primary key, false if not.
     * @throws SQLException
     */
    public boolean isAutoIncremented(String column) throws SQLException {
        if (index.containsKey(column)) {
            return autoInc[index.get(column).intValue()];
        } else {
            final SQLExceptionState state = SQLExceptionState.COLUMN_NOT_FOUND;
            throw new SQLException("Column " + column + " does not exist in dataset.", state.name(), state.code());
        }
    }

    /**
     * Indicates whether the designated numeric column is automatically incremented.
     * @param position the first column is 1, the second is 2, ...
     * @return
     * @throws SQLException
     */
    public boolean isAutoIncremented(int position) throws SQLException {
        if (position <= column.length) {
            return autoInc[position - 1];
        } else {
            final SQLExceptionState state = SQLExceptionState.COLUMN_NOT_FOUND;
            throw new SQLException("The dataset contains only " + String.valueOf(column.length) + " column(s).", state.name(), state.code());
        }
    }

    @Override
    public ArrayList getPriColPosition() {
        return this.primaryKey_col_positions;
    }


}
